/*
 * Course:    CS 590 Web Services
 * Semseter:  Spring 2009
 * Professor: Sedlemeyer
 * Project:   CHEAT (Code Help Educational Assignment Tool)
 * Due Date:  May 1, 2009
 * Team:      Sarcasm
 *            (Baker, Jason; Mitchell, Robin; Shie, Keith)
 */
package cheat.assignment;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import org.web_cat._1_0.assignment_sharing.Assignment;

/**
 * Process an assignmentZip file into its major parts.
 * 
 * @author Keith Shie
 */
public class AssignmentInspector {

  /**
   * Name of the assignment metadata file
   */
  public static final String ASSIGNMENT_META_FILE = "assignment.xml";

  private static final Set requiredFiles =
          Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(new String[]{
            ASSIGNMENT_META_FILE, "assignment.html"})));
  final private Assignment metaBytes;
  final private byte[] studentBytes;
  final private byte[] facultyBytes;

  private AssignmentInspector(Assignment meta, byte[] student, byte[] faculty) {
    this.metaBytes = meta;
    this.studentBytes = student;
    this.facultyBytes = faculty;
  }

  /**
   * Get bytes representing student zip archive.
   *
   * @return bytes representing student zip archive
   */
  public byte[] getStudentBytes() {
    return studentBytes;
  }

  /**
   * Get bytes representing faculty zip archive.
   *
   * @return bytes representing faculty zip archive
   */
  public byte[] getFacultyBytes() {
    return facultyBytes;
  }

  /**
   * Get bytes representing assignment metadata properties file
   * @return bytes representing assignment metadata properties file
   */
  public Assignment getMetaBytes() {
    return metaBytes;
  }

  private static void silent_close(Closeable f) {
    if (f != null) {
      try {
        f.close();
      } catch (IOException ex) {
      }
    }
  }

  private static boolean facultyFile(String name) {
    boolean result = false;

    if (name != null) {
      final String lowerName = name.toLowerCase();

      result = lowerName.startsWith("solution/") || lowerName.startsWith("software-tests/private/");
    }

    return result;
  }

  /**
   * Process an assignmentZip file into its major parts.
   * 
   * @param assignmentZip bytes representing assignment zip archive.
   * 
   * @return an object containing the assignment metadata, student zip bytes, and
   * faculty zip bytes.
   * 
   * @throws java.io.IOException
   * @throws InvalidAssignmentArchiveException 
   */
  @SuppressWarnings( "unchecked" )
  public static AssignmentInspector build(byte[] assignmentZip) throws IOException, InvalidAssignmentArchiveException {
    AssignmentInspector inspector = null;
    final Set<String> missing = new HashSet<String>(requiredFiles);

    final ByteArrayInputStream bais = new ByteArrayInputStream(assignmentZip);
    final ZipInputStream zis = new ZipInputStream(bais);

    final ByteArrayOutputStream commonBaos = new ByteArrayOutputStream(assignmentZip.length);
    final ZipOutputStream commonZos = new ZipOutputStream(commonBaos);

    final ByteArrayOutputStream facultyBaos = new ByteArrayOutputStream();
    final ZipOutputStream facultyZos = new ZipOutputStream(facultyBaos);

    final ByteArrayOutputStream metaBaos = new ByteArrayOutputStream();
    ZipEntry ze;

    try {
      while (null != (ze = zis.getNextEntry())) {
        missing.remove(ze.getName());

        final boolean meta = ze.getName().equalsIgnoreCase(ASSIGNMENT_META_FILE);
        final boolean faculty = facultyFile(ze.getName());
        final OutputStream os = meta ? metaBaos : (faculty ? facultyZos : commonZos);
        if (!meta) {
          (faculty ? facultyZos : commonZos).putNextEntry(ze);
        }
        final byte[] buffer = new byte[10240];
        int bytesRead;
        while (-1 != (bytesRead = zis.read(buffer))) {
          os.write(buffer, 0, bytesRead);
        }
        if (!meta) {
          (faculty ? facultyZos : commonZos).closeEntry();
        }
        zis.closeEntry();
      }

    } finally {
      silent_close(zis);
      silent_close(metaBaos);
      silent_close(facultyZos);
      silent_close(commonZos);
    }

    if ( missing.size() > 0 ){
      throw new InvalidAssignmentArchiveException("required files missing: " + missing);
    } else {
      try {
        final JAXBContext jaxb = JAXBContext.newInstance("org.web_cat._1_0.assignment_sharing");

        final Object obj = jaxb.createUnmarshaller().unmarshal(new ByteArrayInputStream(metaBaos.toByteArray()));

//        System.out.println( "obj: " + ( obj == null ? "{null}" : obj.getClass().getName() ));

        final JAXBElement<Assignment> j = (JAXBElement<Assignment>) obj;

        final Assignment a = j.getValue();
        inspector = new AssignmentInspector(a, commonBaos.toByteArray(), facultyBaos.toByteArray());
      } catch (JAXBException ex) {
        Logger.getLogger(AssignmentInspector.class.getName()).log(Level.SEVERE, null, ex);
        throw new InvalidAssignmentArchiveException( ASSIGNMENT_META_FILE + " can't be understood." );
      }
    }

    return inspector;
  }
}
